//*** Guidance generated code ***//
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Linq.Expressions;

using ProjectBase.Core;
using ProjectBase.Core.Service;

using $prjsn$.Entity;

using $prjsn$.Common;
using $prjsn$.Common.Validation;
using $prjsn$.Common.Data;
using $prjsn$.Common.Business;

namespace $safeprojectname$
{
    public abstract class BusinessObjectValidator<TEntity, TFilter, TDao, TField> : EntityValidatorBase<TEntity, int, TDao>
        where TEntity : BusinessObject
        where TFilter : BusinessObjectFilter, new()
        where TDao : class, IBaseDao<TEntity, int, TFilter>, IHaveMappingInfo<TEntity>
        where TField : class, IField
    {
        private static readonly IList<string> _ignoredProperties = new List<string> { "IsActive", "CountryCode", "ProfileId" };

        protected override void Init(RuleCollection<TEntity> rules)
        {
            FieldFacade.Change += Clear;
            // string length
            rules.For(entity => entity.CountryCode).RequireLengthBetween(0, 50);

            // TODO
            //rules.Evaluate(IsBusinessObjectInUse).WhenDeleting();
            //rules.Assure(CheckFields);
            rules.AddRange(BuildDynamicRules());
        }

        #region Build Rules

        protected virtual IEnumerable<IValidationRule<TEntity>> BuildDynamicRules()
        {
            var rules = DynamicRules;
            if (rules.IsNull())
            {
                if (MappingInfo.IsNull())
                {
                    MappingInfo = GetMapper().MappingInfo ?? new List<MappingDescription>();
                }
                FieldFacade.Change += Clear;

                var reflector = Factory.CreateInstance<DynamicRulesReflector<TEntity, TFilter, TField>>();

                rules = reflector.BuildDynamicRules(MappingInfo, BusinessObjectType);

                DynamicRules = rules;
            }
            return rules;
        }

        private static void Clear(object sender, EventArgs e)
        {
            DynamicRules = null;
            RulesCollection = null;
            //MetaData = null;
        }

        #endregion Build Rules

        #region MetaData

        protected override IList<string> AllwaysIgnoredProperties
        {
            get { return _ignoredProperties.Append(base.AllwaysIgnoredProperties); }
        }

        protected override void EnrichMetaDataAttribute(MDAttribute attribute, MetaDataMessage metaData)
        {
            var isEnriched = TryEnrichArray(attribute)
                           || TryEnrichAdditional(attribute);

            if (!isEnriched)
            {
                TryEnrichStatic(attribute);
            }

            // TODO e.g. Enrich Address
            //TryEnrichCollection(attribute, metaData, entity => entity.Addresses);
        }

        protected virtual bool TryEnrichStatic(MDAttribute attribute)
        {
            var field = FieldFacade
                    .GetAll()
                    .FirstOrDefault(f => f.IsStatic
                                         && f.PropertyName.IsEqual(attribute.Name))
                    ;

            if (field.IsNull())
            {
                return false;
            }

            attribute.DisplayName = field.DisplayName;
            CheckTypesAreSimilar(attribute, field);
            return true;
        }

        protected virtual bool TryEnrichAdditional(MDAttribute attribute)
        {
            if (!attribute.IsAdditional)
            {
                return false;
            }

            var toRemove = new List<MDAttribute>();
            foreach (var additionalAttribute in attribute.Attributes)
            {
                var name = additionalAttribute.Name;
                var field = FieldFacade
                    .GetAll()
                    .SingleOrDefault(f => !f.IsStatic
                                          && f.PropertyName.IsEqual(name))
                    ;

                // known MetaData element, specified in SQL Table
                if (field.Is())
                {
                    additionalAttribute.DisplayName = field.DisplayName;
                    additionalAttribute.SubType = field.SubType;

                    additionalAttribute.CanRead = true;

                    CheckTypesAreSimilar(additionalAttribute, field);
                    continue;
                }

                // In case, there is a MetData for IPersistent e.g. Department
                // and not for its int? DepartmentId
                // try to reuse it
                if (additionalAttribute.Type.IsEquivalentTo(typeof(int))
                    && additionalAttribute.Name.EndsWith("Id", StringComparison.InvariantCultureIgnoreCase))
                {
                    name = name.Substring(0, name.Length - 2);
                    field = FieldFacade
                    .GetAll()
                    .SingleOrDefault(f => !f.IsStatic
                                          && f.PropertyName.IsEqual(name)
                                          && f.Type.HasType<IPersistentObject>())
                    ;

                    if (field.Is())
                    {
                        additionalAttribute.DisplayName = field.DisplayName + " Id";
                        additionalAttribute.SubType = field.SubType;
                        additionalAttribute.CanEdit = true;
                        additionalAttribute.CanRead = true;

                        continue;
                    }
                }

                // some NHibernate element to be removed
                toRemove.Add(additionalAttribute);
            }
            foreach (var mdAttribute in toRemove)
            {
                attribute.Attributes.Remove(mdAttribute);
            }
            return true;
        }

        protected virtual bool TryEnrichArray(MDAttribute attribute)
        {
            if (!attribute.IsArray)
            {
                return false;
            }
            // pair table for the array
            var field = FieldFacade
                .GetAll()
                .First(f => f.PropertyName.IsEqual(attribute.Name))
                ;

            if (field.Is())
            {
                attribute.DisplayName = field.DisplayName;
                attribute.PersistenceName = field.TableName;
            }
            return true;
        }

        protected virtual bool TryEnrichCollection<TProperty>(
            MDAttribute attribute
            , MetaDataMessage metaData
            , Expression<Func<TEntity, IEnumerable<TProperty>>> expression)
            where TProperty : class, IPersistentObjectWithTypedId<int>
        {
            var body = GetMemberExpression(expression);

            var propertyName = body.Member.Name;
            var type = typeof(TProperty);
            var typeName = type.Name;

            // Not special case
            if (!attribute.DataType.IsEqual(typeName))
            {
                return false;
            }
            attribute.CanEdit = true;
            attribute.PersistenceName = null;
            attribute.DisplayName = propertyName;

            // internal attributes
            var collectionMDProvider = Factory.CreateInstance<IMetaDataProvider<TProperty, int>>();
            var collectionMeta = collectionMDProvider.GetMetaData();

            attribute.Attributes = collectionMeta.Attributes;
            foreach (var attr in attribute.Attributes)
            {
                attr.CanQuery = false;

                // e.g. Language of ConatctSalutation
                if (attr.Type.HasType<IPersistentObject>())
                {
                    attr.CanRead = false;
                    attr.CanEdit = true;
                }
            }
            return true;
        }

        private static MemberExpression GetMemberExpression<TProperty>(Expression<Func<TEntity, IEnumerable<TProperty>>> expression)
        {
            MemberExpression body;
            switch (expression.Body.NodeType)
            {
                case ExpressionType.MemberAccess:
                    body = expression.Body as MemberExpression;
                    break;

                default:
                    throw new NotSupportedException(
                        "Provided 'Expression<Func<TEntity, IEnumerable<TProperty>>> expression' parameter must be of a 'ExpressionType.MemberAccess'. ");
            }
            return body;
        }

        public static void CheckTypesAreSimilar(MDAttribute attribute, IField field)
        {
            if (field.IsNull())
            {
                // TODO - all fields should be in DB
                return;
            }

            var attributeType = attribute.Type;
            var fieldType = field.Type;

            if (attributeType.IsNull())
            {
                throw new ConfigurationErrorsException("Missing attribute 'Type', property {0}".FormatWith(attribute.Name));
            }
            if (fieldType.IsNull())
            {
                throw new ConfigurationErrorsException("Missing field 'Type', property {0}".FormatWith(field.PropertyName));
            }

            if (fieldType.IsEquivalentTo(attributeType))
            {
                return;
            }

            var areValueTypes = attributeType.IsValueType
                && fieldType.IsValueType
                && fieldType.IsGenericType
                && fieldType.GetGenericArguments()[0].IsEquivalentTo(attributeType)
                && attribute.IsNullable
                ;

            if (areValueTypes)
            {
                return;
            }

            throw new ConfigurationErrorsException(
                                "Incorrect mapping/meta data of the attribute {0}, the C# type is [{1}], while meta type is [{2}] ".FormatWith(attribute.Name, attribute.Type.Name, field.Type.Name)
                                );
        }

        #endregion MetaData

        #region properties

        protected abstract BusinessObjectTypes BusinessObjectType { get; }

        protected static IList<MappingDescription> MappingInfo { get; set; }

        protected static IEnumerable<IValidationRule<TEntity>> DynamicRules { get; set; }

        public virtual IFieldObjectFacade<TField> FieldFacade { protected get; set; }

        #endregion properties
    }
}